package com.mySpringMvc.web.servlet;

import com.mySpringMvc.context.WebApplicationContext;
import com.mySpringMvc.web.constant.Const;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;

import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @program: mySSM
 * @description: 前端控制器
 * @author:wjp
 * @create: 2025-02-13 18:08
 **/
public class DispatcherServlet extends HttpServlet {

    private ViewResolver viewResolver;

    private HandlerMapping handlerMapping;

    private HandlerAdapter handlerAdapter;

    /**
     * 初始化Spring Web容器
     * 注意不要重写带config参数的init方法，web容器初始化时，
     * 会调用父类HttpServlet的父类会调用父类HttpServlet的父类GenericServlet的init(init(ServletConfig config))方法
     * 此时会调用父类HttpServlet的父类GenericServlet的init的init方法会为config属性赋值
     * 然后继续调用init()不带参数的init方法，因此我们只需要重写不带config参数的init方法即可
     * 如果重写了带config参数的init方法可能会导致config对象无法被成功赋值（可手动调用父类的init方法，但依旧不推荐）
     * 导致一系列的异常情况
     * @throws ServletException 异常
     */
    @SneakyThrows
    @Override
    public void init() throws ServletException {
        /**
         *         <init-param>
         *             <param-name>contextConfigLocation</param-name>
         *             <param-value>classpath:springmvc.xml</param-value>
         *         </init-param>
         *         以上信息会被封装到servletConfig对象中(web容器创建，调用init(ServletConfig config) 赋值)
         *         通过getServletConfig可获取ServletConfig最想
         */
        // 通过web.xml配置的初始化参数找到springmvc的配置文件
        ServletConfig servletConfig = getServletConfig();
        String contextConfigLocation = servletConfig.getInitParameter(Const.CONTEXT_CONFIG_LOCATION);
        String configPath = null;
        if (contextConfigLocation.trim().startsWith(Const.CLASSPATH_PREFIX)){
            // 配置文件需要从类路径中查找（通过类加载器获取资源的绝对路径）
            configPath = Thread.currentThread().getContextClassLoader().getResource(contextConfigLocation.substring(Const.CLASSPATH_PREFIX.length())).getPath();
            // 此时的configPath可能由于字符编码的问题处于urlEncode的状态,因此需要进行解码
            configPath = URLDecoder.decode(configPath, Charset.defaultCharset());
        }
        // 通过配置文件的配置初始化SpringMVC容器
        WebApplicationContext webApplicationContext = new WebApplicationContext(configPath, this.getServletContext());
        // webApplicationContext 代表spring web容易，为了方便后续使用，将其存储到servletContext中
        this.getServletContext().setAttribute(Const.WEB_APPLICATION_CONTEXT, webApplicationContext);

        // 初始化视图解析器、处理器映射器，处理器适配器
        viewResolver = (ViewResolver) webApplicationContext.getBean("internalResourceViewResolver");
        handlerMapping = (HandlerMapping) webApplicationContext.getBean("requestMappingHandlerMapping");
        handlerAdapter = (HandlerAdapter) webApplicationContext.getBean("requestMappingHandlerAdapter");

    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    /**
     * 处理器请求分发
     * @param req 请求
     * @param resp 相应
     * @throws ServletException 异常
     * @throws IOException 异常
     */
    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
        /**
         * 处理用户请求
         * 1. 首先根据请求路径，使用处理器映射器获取处理器执行链对象
         *    处理器执行链对象包括本次请求所有的拦截器对象以及执行方法对象
         * 2. 根据处理器执行链对象中的处理器方法对象获取对应的处理器适配器对象
         * 3. 执行本次执行链所有的拦截器的preHandle方法
         * 4. 通过处理器适配器执行处理器方法并返回modalAndView
         * 5. 执行本次执行链所有拦截器的postHandle方法
         * 6. 进行处理器结果分发
         *  6.1 根据视图解析器获取视图对象
         *  6.2 调用视图对象的render方法进行视图渲染
         *  6.3 执行本次执行链所有拦截器的afterCompletion方法
         */
        try {
//            1. 首先根据请求路径，使用处理器映射器获取处理器执行链对象
            HandlerExecutionChain mappedHandler = handlerMapping.getHandler(req);
//            2. 根据处理器执行链对象中的处理器方法对象获取对应的处理器适配器对象( 由于此演示项目仅有一个RequestAdapter，因此直接使用该adapter调用目标方法)
//            this.handlerAdapter = this.handlerAdapter;
//            3. 执行本次执行链所有的拦截器的preHandle方法
            boolean b = mappedHandler.applyPreHandle(req,resp);
            if (!b){
                return;
            }
//            4. 通过处理器适配器执行处理器方法并返回modalAndView
            ModalAndView modalAndView = handlerAdapter.handle(req, resp, mappedHandler.getHandler());
//            5. 执行本次执行链所有拦截器的postHandle方法
            mappedHandler.applyPostHandle(req,resp,modalAndView);
//            6.1 根据视图解析器获取视图对象
            View view = viewResolver.resolveViewName(modalAndView.getViewName(), req.getLocale());
//            6.2 调用视图对象的render方法进行视图渲染
            view.render(modalAndView.getModalMap(),req,resp);
//            6.3 执行本次执行链所有拦截器的afterCompletion方法
            mappedHandler.triggerAfterCompletion(req,resp,modalAndView);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
